// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: GPL-3.0-or-later

package xyz.apiote.bimba.czwek.repo

import android.content.Context
import android.net.ConnectivityManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import xyz.apiote.bimba.czwek.api.LineV1
import xyz.apiote.bimba.czwek.api.LineV2
import xyz.apiote.bimba.czwek.api.PositionV1
import xyz.apiote.bimba.czwek.api.Server
import xyz.apiote.bimba.czwek.api.StopV1
import xyz.apiote.bimba.czwek.api.StopV2
import xyz.apiote.bimba.czwek.api.UnknownResourceException
import xyz.apiote.bimba.czwek.api.VehicleV1
import xyz.apiote.bimba.czwek.api.VehicleV2
import xyz.apiote.bimba.czwek.api.VehicleV3
import xyz.apiote.bimba.czwek.api.responses.DeparturesResponse
import xyz.apiote.bimba.czwek.api.responses.DeparturesResponseDev
import xyz.apiote.bimba.czwek.api.responses.DeparturesResponseV1
import xyz.apiote.bimba.czwek.api.responses.DeparturesResponseV2
import xyz.apiote.bimba.czwek.api.responses.DeparturesResponseV3
import xyz.apiote.bimba.czwek.api.responses.ErrorResponse
import xyz.apiote.bimba.czwek.api.responses.FeedsResponse
import xyz.apiote.bimba.czwek.api.responses.FeedsResponseDev
import xyz.apiote.bimba.czwek.api.responses.FeedsResponseV1
import xyz.apiote.bimba.czwek.api.responses.FeedsResponseV2
import xyz.apiote.bimba.czwek.api.responses.LineResponse
import xyz.apiote.bimba.czwek.api.responses.LineResponseDev
import xyz.apiote.bimba.czwek.api.responses.LineResponseV1
import xyz.apiote.bimba.czwek.api.responses.LineResponseV2
import xyz.apiote.bimba.czwek.api.responses.LocatablesResponse
import xyz.apiote.bimba.czwek.api.responses.LocatablesResponseDev
import xyz.apiote.bimba.czwek.api.responses.LocatablesResponseV1
import xyz.apiote.bimba.czwek.api.responses.LocatablesResponseV2
import xyz.apiote.bimba.czwek.api.responses.LocatablesResponseV3
import xyz.apiote.bimba.czwek.api.responses.QueryablesResponse
import xyz.apiote.bimba.czwek.api.responses.QueryablesResponseDev
import xyz.apiote.bimba.czwek.api.responses.QueryablesResponseV1
import xyz.apiote.bimba.czwek.api.responses.QueryablesResponseV2
import xyz.apiote.bimba.czwek.api.responses.QueryablesResponseV3
import java.io.File
import java.net.URLEncoder

// todo [3.2] in Repository check if responses are BARE or HTML

class OnlineRepository : Repository {
	private fun saveFeedCache(server: Server, context: Context, rawResponse: ByteArray) {
		val file = File(
			context.filesDir, URLEncoder.encode(server.apiPath, "utf-8")
		)
		file.writeBytes(rawResponse)
	}

	override suspend fun getFeeds(
		context: Context,
		server: Server
	): Map<String, FeedInfo>? {
		val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
		val result =
			xyz.apiote.bimba.czwek.api.getFeeds(cm, server)
		if (result.error != null) {
			if (result.stream != null) {
				val response = withContext(Dispatchers.IO) { ErrorResponse.unmarshal(result.stream) }
				throw TrafficResponseException(result.error.statusCode, response.message, result.error)
			} else {
				throw TrafficResponseException(result.error.statusCode, "", result.error)
			}
		} else {
			val rawResponse = result.stream!!.readBytes()
			saveFeedCache(server, context, rawResponse)
			return when (val response =
				withContext(Dispatchers.IO) { FeedsResponse.unmarshal(rawResponse.inputStream()) }) {
				is FeedsResponseDev -> response.feeds.associate { Pair(it.id, FeedInfo(it)) }
				is FeedsResponseV2 -> response.feeds.associate { Pair(it.id, FeedInfo(it)) }
				is FeedsResponseV1 -> response.feeds.associate { Pair(it.id, FeedInfo(it)) }

				else -> null
			}
		}
	}

	override suspend fun getDepartures(
		feedID: String,
		stop: String,
		line: String?,
		context: Context,
		limit: Int?
	): StopDepartures? {
		val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
		val result =
			xyz.apiote.bimba.czwek.api.getDepartures(cm, Server.get(context), feedID, stop, line, limit)
		if (result.error != null) {
			if (result.stream != null) {
				val response = withContext(Dispatchers.IO) { ErrorResponse.unmarshal(result.stream) }
				throw TrafficResponseException(result.error.statusCode, response.message, result.error)
			} else {
				throw TrafficResponseException(result.error.statusCode, "", result.error)
			}
		} else {
			return when (val response =
				withContext(Dispatchers.IO) { DeparturesResponse.unmarshal(result.stream!!) }) {
				is DeparturesResponseDev -> StopDepartures(
					response.departures.map { Departure(it) },
					Stop(response.stop),
					response.alerts.map { Alert(it) })

				is DeparturesResponseV3 -> StopDepartures(
					response.departures.map { Departure(it) },
					Stop(response.stop),
					response.alerts.map { Alert(it) })

				is DeparturesResponseV2 -> StopDepartures(
					response.departures.map { Departure(it) },
					Stop(response.stop),
					response.alerts.map { Alert(it) })

				is DeparturesResponseV1 -> StopDepartures(
					response.departures.map { Departure(it) },
					Stop(response.stop),
					response.alerts.map { Alert(it) })

				else -> null
			}
		}
	}

	override suspend fun getLocatablesIn(
		cm: ConnectivityManager,
		bl: Position,
		tr: Position,
		context: Context
	): List<Locatable>? {
		val result = xyz.apiote.bimba.czwek.api.getLocatablesIn(
			cm,
			Server.get(context),
			PositionV1(bl.latitude, bl.longitude),
			PositionV1(tr.latitude, tr.longitude)
		)
		if (result.error != null) {
			if (result.stream != null) {
				val response = withContext(Dispatchers.IO) { ErrorResponse.unmarshal(result.stream) }
				throw TrafficResponseException(result.error.statusCode, response.message, result.error)
			} else {
				throw TrafficResponseException(result.error.statusCode, "", result.error)
			}
		} else {
			return when (val response =
				withContext(Dispatchers.IO) { LocatablesResponse.unmarshal(result.stream!!) }) {
				is LocatablesResponseDev -> response.locatables.map {
					when (it) {
						is StopV2 -> Stop(it)
						is VehicleV3 -> Vehicle(it)
						else -> throw UnknownResourceException("locatables", it::class)
					}
				}

				is LocatablesResponseV3 -> response.locatables.map {
					when (it) {
						is StopV2 -> Stop(it)
						is VehicleV3 -> Vehicle(it)
						else -> throw UnknownResourceException("locatables", it::class)
					}
				}

				is LocatablesResponseV2 -> response.locatables.map {
					when (it) {
						is StopV2 -> Stop(it)
						is VehicleV2 -> Vehicle(it)
						else -> throw UnknownResourceException("locatables", it::class)
					}
				}

				is LocatablesResponseV1 -> response.locatables.map {
					when (it) {
						is StopV1 -> Stop(it)
						is VehicleV1 -> Vehicle(it)
						else -> throw UnknownResourceException("locatables", it::class)
					}
				}

				else -> null
			}
		}
	}

	override suspend fun getLine(
		cm: ConnectivityManager, feedID: String, line: String, context: Context
	): Line? {
		val result = xyz.apiote.bimba.czwek.api.getLine(cm, Server.get(context), feedID, line)
		if (result.error != null) {
			if (result.stream != null) {
				val response = withContext(Dispatchers.IO) { ErrorResponse.unmarshal(result.stream) }
				throw TrafficResponseException(result.error.statusCode, response.message, result.error)
			} else {
				throw TrafficResponseException(result.error.statusCode, "", result.error)
			}
		} else {
			return when (val response =
				withContext(Dispatchers.IO) { LineResponse.unmarshal(result.stream!!) }) {
				is LineResponseDev -> Line(response.line)
				is LineResponseV1 -> Line(response.line)
				is LineResponseV2 -> Line(response.line)
				else -> null
			}
		}
	}

	override suspend fun queryQueryables(
		query: String, context: Context
	): List<Queryable>? {
		return getQueryables(query, null, context, "query")
	}

	override suspend fun locateQueryables(
		position: Position, context: Context
	): List<Queryable>? {
		return getQueryables(null, position, context, "locate")
	}

	private suspend fun getQueryables(
		query: String?, position: Position?, context: Context, type: String
	): List<Queryable>? {
		val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
		val result = when (type) {
			"query" -> {
				xyz.apiote.bimba.czwek.api.queryQueryables(cm, Server.get(context), query!!, limit = 12)
			}

			"locate" -> xyz.apiote.bimba.czwek.api.locateQueryables(
				cm, Server.get(context), PositionV1(position!!.latitude, position.longitude)
			)

			else -> throw RuntimeException("Unknown query type $type")
		}
		if (result.error != null) {
			if (result.stream != null) {
				val response = withContext(Dispatchers.IO) { ErrorResponse.unmarshal(result.stream) }
				throw TrafficResponseException(result.error.statusCode, response.message, result.error)
			} else {
				throw TrafficResponseException(result.error.statusCode, "", result.error)
			}
		} else {
			return when (val response =
				withContext(Dispatchers.IO) { QueryablesResponse.unmarshal(result.stream!!) }) {
				is QueryablesResponseDev -> response.queryables.map {
					when (it) {
						is StopV2 -> Stop(it)
						is LineV2 -> Line(it)
						else -> throw UnknownResourceException("queryablesV2", it::class)
					}
				}

				is QueryablesResponseV1 -> response.queryables.map {
					when (it) {
						is StopV1 -> Stop(it)
						else -> throw UnknownResourceException("queryablesV1", it::class)
					}
				}

				is QueryablesResponseV2 -> response.queryables.map {
					when (it) {
						is StopV2 -> Stop(it)
						is LineV1 -> Line(it)
						else -> throw UnknownResourceException("queryablesV2", it::class)
					}
				}

				is QueryablesResponseV3 -> response.queryables.map {
					when (it) {
						is StopV2 -> Stop(it)
						is LineV2 -> Line(it)
						else -> throw UnknownResourceException("queryablesV2", it::class)
					}
				}

				else -> null
			}
		}
	}
}
